﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Clandestine.Entities;
using Clandestine;
using System.Drawing;
using System.Xml;
using System.IO;

namespace Slick.MapEditor
{
    public class Map
    {
        public LayerManager Layers;
        public EntityManager Entities;

        public Map()
        {
            // NOTHING IN HERE CAN CAUSE EDITOR TO USE MapMode! :)
            Layers = new LayerManager();
            Entities = new EntityManager(this);
        }

        public void Load(string filename)
        {
            // Open the file in filename
            XmlTextReader xml = new XmlTextReader(File.Open(filename, FileMode.Open));

            // Reset the editor as best we can
            Program.ResetLevel();

            // Read that bitch.
            while (xml.Read())
            {
                // We only care about start nodes here, rly
                if (xml.NodeType == XmlNodeType.Element)
                {
                    switch (xml.Name)
                    {
                        case "map":
                            if (xml.AttributeCount > 2)
                                Program.GridSize = int.Parse(xml.GetAttribute("gridsize"));
                            else
                            {
                                Program.GridSize = 16;
                                Log.e("No grid size, assuming 16x16.");
                                Log.e("Support for default grid sizes are going away VERY soon!");
                            }
                            Program.ReloadTilesets();
                            break;

                        case "spritesheets":

                            // ??????? WHY DO WE LOAD PER MAP
                            // SHOULDN'T THEY ALREADY BE LOADED?
                            // ??????????????????????????????????
                            // ??????????????????????????????????

                            // Okay, now we make a new little loop till we've loaded all spritesheets
                            while ((xml.NodeType != XmlNodeType.EndElement) || (xml.Name != "spritesheets"))
                            {
                                if ((xml.NodeType == XmlNodeType.Element) && (xml.Name == "spritesheet"))
                                    Tilesets.LoadTileset(xml.GetAttribute("name"));
                                xml.Read();
                            }

                            break;
                        case "layers":

                            // Okay, now we make a new little loop till we've loaded all the layers
                            while ((xml.NodeType != XmlNodeType.EndElement) || (xml.Name != "layers"))
                            {
                                if ((xml.NodeType == XmlNodeType.Element) && (xml.Name == "layer"))
                                {
                                    Layer l = new Layer();
                                    string depth = xml.GetAttribute("depth");
                                    l.Depth = decimal.Parse(depth);

                                    // Parse the layer data.
                                    string layerData = xml.ReadElementContentAsString();
                                    l.Open(layerData);

                                    Layers.Add(l);
                                }
                                xml.Read();
                            }

                            break;

                        case "entities":
                            if (!xml.IsEmptyElement)
                                Entities.LoadEntities(xml);
                            break;
                    }
                }
            }

            xml.Close();
        }

        public void Save(string filename)
        {
            // Start the XML document.
            XmlTextWriter xml = new XmlTextWriter(filename, null);
            xml.Formatting = Formatting.Indented;
            xml.WriteStartDocument();

            // Get map dimensions
            Rectangle mapDimensions = findMapDimensions();

            // Map definition!
            xml.WriteStartElement("map");
            xml.WriteAttributeString("width", mapDimensions.Width.ToString());
            xml.WriteAttributeString("height", mapDimensions.Height.ToString());
            xml.WriteAttributeString("gridsize", Program.GridSize.ToString());

            // Tilesets
            Dictionary<string, int> tilesetToIndexMapping = Tilesets.Save(xml);

            // Layers!
            xml.WriteStartElement("layers");
            lock (Layers)
            {
                // Actually write the layers!
                foreach (Layer l in Layers.Layers)
                    l.Save(xml, mapDimensions.Y, mapDimensions.Height + mapDimensions.Y, mapDimensions.X, tilesetToIndexMapping);
            }
            xml.WriteEndElement(); // </layers>

            // Entities
            Entities.SaveEntities(xml, mapDimensions.X * Program.GridSize, mapDimensions.Y * Program.GridSize);

            // Finish off
            xml.WriteEndElement();
            xml.WriteEndDocument();
            xml.Close();
        }

        private Rectangle findMapDimensions()
        {
            // Finds true map height etc.
            int miny = int.MaxValue;
            int maxy = int.MinValue;
            int minx = int.MaxValue;
            int maxx = int.MinValue;

            // Go through layers and find!
            lock (Layers)
            {
                foreach (Layer j in Layers.Layers) // TODO: MAKE IENUMERABLE
                {
                    int temp = j.GetMinLine();
                    if (temp < miny)
                        miny = temp;
                    temp = j.GetMaxLine();
                    if (temp > maxy)
                        maxy = temp;
                    temp = j.GetMinX();
                    if (temp < minx)
                        minx = temp;
                    temp = j.GetMaxX();
                    if (temp > maxx)
                        maxx = temp;
                }
            }


            // Rect = [minx, miny, width, height]
            return new Rectangle(minx, miny, maxx - minx, maxy - miny);
        }

        public void PaintTile(int tileX, int tileY, bool tempOnly)
        {
            try
            {
                // If it's a temp paint (eg. previewing tiles) then paint to the temp layer, in position of selected layer.
                if (tempOnly)
                    lock (Layers)
                        Layers.TempLayer.Depth = Layers.SelectedLayer.Depth + 0.001M;

                // Pain dem tiles!
                lock (Tilesets.SelectedTiles)
                {
                    if (Tilesets.SelectedTiles.Count != 0)
                        lock (Layers)
                            for (int yOffset = 0; yOffset < Tilesets.SelectedTiles.Count; yOffset++)
                                for (int xOffset = 0; xOffset < Tilesets.SelectedTiles[yOffset].Count; xOffset++)
                                {
                                    // FUCK KNOWS what this line does. I can't be arsed decoding it and documenting it.
                                    // I shall assume it works 100% perfectly and leave well alone.
                                    if (!Texture.TextureExists(Tilesets.SelectedTiles[yOffset][xOffset]))
                                        Tilesets.CutSingleTile(Tilesets.SelectedTiles[yOffset][xOffset].Split('!')[0],
                                            int.Parse(Tilesets.SelectedTiles[yOffset][xOffset].Split('!')[1]));
                                    (!tempOnly ? Layers.SelectedLayer : Layers.TempLayer)
                                        .SetTile(tileX + xOffset, tileY + yOffset,
                                        Texture.GetTexture(Tilesets.SelectedTiles[yOffset][xOffset]));
                                }
                }
            }
            catch (Exception e)
            {
                Log.e("Failed to set tile on layer b/c " + e.ToString());
            }
        }

        public void EraseTile(int tileX, int tileY)
        {
            try
            {
                lock (Layers)
                    Layers.SelectedLayer.ClearTile(tileX, tileY);
            }
            catch (Exception e)
            {
                Log.e("Failed to erase tile on layer '{0}', depth '{1}' b/c {2}", Layers.SelectedLayerID, Layers.SelectedLayer.Depth, e);
            }
        }

    }
}
